package github.sf.fw.tools.db.ddldiff;

import com.alibaba.druid.sql.ast.SQLDataType;
import com.alibaba.druid.sql.ast.SQLExpr;
import com.alibaba.druid.sql.ast.SQLName;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition;
import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.druid.sql.visitor.SQLASTVisitor;
import github.sf.fw.utils.StrUtil;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;

public abstract class DDLUpdater {


    /**
     * 根据老的建表语句和新的建表语句创建表更新语句,
     * 暂只支持老表和新表的列顺序要相同，且新表不能在表的中间修改字段值，否则会直接使用新表的建表语句
     *
     * @param oldDDL 表格原来的建表语句
     * @param newDDL 表格新的建表语句
     * @return 表格更新语句
     */
    public abstract String createUpdateTableDDL(String oldDDL, String newDDL);

    public abstract String smartCreateUpdateTableDDL(String oldDDL, String newDDL);

    /**
     * 获取DDL中column相关信息
     *
     * @param ddl createTableDDL
     */
    public Pair<String, List<SQLColumnDefinition>> getColumns(String ddl) {
        String[] multiSql = ddl.split(";");
        List<SQLColumnDefinition> columnNames = new ArrayList<>();
        AtomicReference<String> tableName = new AtomicReference<>();
        SQLASTVisitor columnVisitor = new SQLASTVisitor() {
            @Override
            public boolean visit(SQLCreateTableStatement x) {
                SQLName name = x.getName();
                String simpleName = name.getSimpleName();
                tableName.set(simpleName);
                return true;
            }

            @Override
            public boolean visit(SQLColumnDefinition x) {
                columnNames.add(x);
                return true;
            }
        };
        for (String sql : multiSql) {
            sql = sql.trim();
            if (StrUtil.isEmpty(sql) || StringUtils.startsWithIgnoreCase(sql, "drop")) {
                continue;
            }
            SQLStatementParser sqlStatementParser = new SQLStatementParser(sql);
            for (SQLStatement sqlStatement : sqlStatementParser.parseStatementList()) {
                sqlStatement.accept(columnVisitor);
            }
        }
        return Pair.of(tableName.get(), columnNames);
    }

    public String getLength(SQLDataType dataType) {
        List<SQLExpr> arguments = dataType.getArguments();
        if (arguments != null && !arguments.isEmpty()) {
            return arguments.get(0).toString();
        }
        return "0";
    }


    /**
     * 判断两列是否相等
     * 1.先判断列名是否相等，区分大小写
     * 2.判断类型是否相等
     * 3.判断类中带的参数是否相等
     *
     * @param columnDefinition1 列1
     * @param columnDefinition2 列2
     * @return 是否相等
     */
    public static boolean columnEqual(SQLColumnDefinition columnDefinition1, SQLColumnDefinition columnDefinition2) {
        if (!equalIgnoreSpecialChar(columnDefinition1.getColumnName(), columnDefinition2.getColumnName())) {
            return false;
        }
        if (!columnDefinition1.getDataType().getName().equals(columnDefinition2.getDataType().getName())) {
            return false;
        }
        List<SQLExpr> arguments1 = columnDefinition1.getDataType().getArguments();
        List<SQLExpr> arguments2 = columnDefinition2.getDataType().getArguments();
        if (CollectionUtils.isEmpty(arguments1) && CollectionUtils.isEmpty(arguments2)) {
            return true;
        }
        if (!CollectionUtils.isEmpty(arguments1) && !CollectionUtils.isEmpty(arguments2)) {
            if (arguments1.size() != arguments2.size()) {
                return false;
            }
            for (int i = 0; i < arguments1.size(); i++) {
                SQLExpr sqlExpr1 = arguments1.get(i);
                SQLExpr sqlExpr2 = arguments2.get(i);
                if (!sqlExpr1.equals(sqlExpr2)) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    public static String columnDefinitionUnique(SQLColumnDefinition definition) {
        String columnName = definition.getColumnName();
        columnName = columnName.replaceAll("`", "");
        String name = definition.getDataType().toString();
        return columnName + name;
    }

    public static boolean nonEqualIgnoreSpecialChar(String name1, String name2) {
        return !equalIgnoreSpecialChar(name1, name2);
    }

    public static boolean equalIgnoreSpecialChar(String name1, String name2) {
        if (!StrUtil.isEmpty(name1) && !StrUtil.isEmpty(name2)) {
            if (name1.equals(name2)) {
                return true;
            }
            name1 = name1.replaceAll("`", "");
            name2 = name2.replaceAll("`", "");
            return name1.equals(name2);
        }
        return StrUtil.isEmpty(name1) && StrUtil.isEmpty(name2);
    }

    public static String pureColumnName(String columnName) {
        return columnName.replaceAll("`", "");
    }

    /**
     * 分析传入的两个SQLColumnDefinition,并生成对应的SQLColumnDefinitionUpdateStatus,后续会根据分析出的ColumnDefinitionUpdateStatus
     * 生成对应的更新语句
     *
     * @param oldColumnDefinitions 列定义1
     * @param newColumnDefinitions 列定义2
     * @return SQLColumnDefinitionAnalysisContext
     */
    protected abstract SQLColumnDefinitionAnalysisContext analyzeColumnDefinition(List<SQLColumnDefinition> oldColumnDefinitions, List<SQLColumnDefinition> newColumnDefinitions);

    /**
     * 根据传入的sqlColumnDefinitionUpdateStatuses生成对应的update语句
     *
     * @param tableName                         需要更新的表格名称
     * @param sqlColumnDefinitionUpdateStatuses 列定义操作
     * @return 更新语句
     */
    protected abstract List<String> createTableUpdateDDL(String tableName, List<SQLColumnDefinitionUpdate> sqlColumnDefinitionUpdateStatuses);

    public static class SQLColumnDefinitionAnalysisContext {
        List<SQLColumnDefinitionUpdate> sqlColumnDefinitionUpdateStatuses;

        boolean useNewDDL = true;

        public List<SQLColumnDefinitionUpdate> getSqlColumnDefinitionUpdateStatuses() {
            return sqlColumnDefinitionUpdateStatuses;
        }

        public void setSqlColumnDefinitionUpdateStatuses(List<SQLColumnDefinitionUpdate> sqlColumnDefinitionUpdateStatuses) {
            this.sqlColumnDefinitionUpdateStatuses = sqlColumnDefinitionUpdateStatuses;
        }

        public boolean isUseNewDDL() {
            return useNewDDL;
        }

        public void setUseNewDDL(boolean useNewDDL) {
            this.useNewDDL = useNewDDL;
        }
    }

    public static class SQLColumnDefinitionUpdate {
        SQLColumnDefinition sqlColumnDefinition;

        ColumnDefinitionUpdateStatus columnDefinitionUpdateStatus;

        InsertType insertType;
        SQLColumnDefinition locationColumnDefinition;


        public SQLColumnDefinitionUpdate(SQLColumnDefinition sqlColumnDefinition, ColumnDefinitionUpdateStatus columnDefinitionUpdateStatus) {
            this.sqlColumnDefinition = sqlColumnDefinition;
            this.columnDefinitionUpdateStatus = columnDefinitionUpdateStatus;
        }

        public SQLColumnDefinition getSqlColumnDefinition() {
            return sqlColumnDefinition;
        }

        public ColumnDefinitionUpdateStatus getColumnDefinitionUpdateStatus() {
            return columnDefinitionUpdateStatus;
        }

        public InsertType getInsertType() {
            return insertType;
        }

        public void setInsertType(InsertType insertType) {
            this.insertType = insertType;
        }

        public SQLColumnDefinition getLocationColumnDefinition() {
            return locationColumnDefinition;
        }

        public void setLocationColumnDefinition(SQLColumnDefinition locationColumnDefinition) {
            this.locationColumnDefinition = locationColumnDefinition;
        }
    }


    /**
     * SQL列定义状态
     */
    public enum ColumnDefinitionUpdateStatus {
        ADD, DROP, MODIFY, MODIFY_AND_MOVE, MOVE
    }

    public enum InsertType {
        before,
        after;
    }
}
